// Copyright (C) 2013 Alex-97
// This file is part of textpad-editor.
//
// textpad-editor is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// textpad-editor is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with textpad-editor.  If not, see <http://www.gnu.org/licenses/>.

#include "headers/mainwindow.h"
#include "headers/highlighter.h"

/****************************************************************************
 **
 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
 ** All rights reserved.
 ** Contact: Nokia Corporation (qt-info@nokia.com)
 **
 ** This file is part of the examples of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
 **     the names of its contributors may be used to endorse or promote
 **     products derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

 #include <QtGui>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QtConcurrentRun>

 Highlighter::Highlighter(QTextDocument *parent)
     : QSyntaxHighlighter(parent)
 {
     bSpellCheck = false;
     HighlightingRule rule;


     keywordFormat.setForeground(Qt::blue);
     //keywordFormat.setFontWeight(QFont::Bold);
     QStringList keywordPatterns;
     keywordPatterns
             /*
             << "\\bchar\\b" << "\\bclass\\b" << "\\bconst\\b"
                     << "\\bdouble\\b" << "\\benum\\b" << "\\bexplicit\\b"
                     << "\\bfriend\\b" << "\\binline\\b" << "\\bint\\b"
                     << "\\blong\\b" << "\\bnamespace\\b" << "\\boperator\\b"
                     << "\\bprivate\\b" << "\\bprotected\\b" << "\\bpublic\\b"
                     << "\\bshort\\b" << "\\bsignals\\b" << "\\bsigned\\b"
                     << "\\bslots\\b" << "\\bstatic\\b" << "\\bstruct\\b"
                     << "\\btemplate\\b" << "\\btypedef\\b" << "\\btypename\\b"
                     << "\\bunion\\b" << "\\bunsigned\\b" << "\\bvirtual\\b"
                     << "\\bvoid\\b" << "\\bvolatile\\b"
                        */
                     << "\\\\section"<<"\\\\item"<<"\\\\subsection"<<"\\\\subsubsection"
                     << "\\\\chapter"<<"\\\\begin"<<"\\\\end"<<"\\\\usepackage"<<"\\\\document"
                     <<"\\\\cite"<<"\\\\upcite"<<"\\\\ref"<<"\\\\label"<<"\\\\caption"
                     <<"\\\\hline"<<"\\\\cline"<<"\\\\toprule"<<"\\\\bottomrule"<<"\\\\\\\\"
                    <<"\\{flalign\\}"<<"\\&"<<"\\\\;"
                   <<"\\{subarray\\}"<<"\\{equation\\}"<<"\\{cases\\}"<<"\\{array\\}"
                   <<"\\\\bibliographystyle"<<"\\\\title"<<"\\\\footnote"<<"\\\\includegraphics"
                  <<"\\\\centering"<<"\\\\vspace"<<"\\\\scriptsize"
                 <<"\\\\bibitem"<<"\\{thebibliography\\}"<<"\\{abstract\\}"<<"\\\\\%"
                        ;
     foreach (const QString &pattern, keywordPatterns) {
         rule.pattern = QRegExp(pattern);
         rule.format = keywordFormat;
         highlightingRules.append(rule);
     }


     //greaks
     equationGreeks.setForeground(Qt::darkMagenta);
     equationGreeks.setFontItalic(false);
     QStringList greekList;
     greekList<<"\\\\alpha"<<"\\\\beta"<<"\\\\gamma"<<"\\\\delta"<<"\\\\epsilon"<<"\\\\zeta"<<"\\\\eta"
                <<"\\\\theta"<<"\\\\iota"<<"\\\\kappa"<<"\\\\lambda"<<"\\\\mu"<<"\\\\xi"<<"\\\\nu"<<"\\\\pi"
              <<"\\\\rho"<<"\\\\sigma"<<"\\\\tau"<<"\\\\upsilon"<<"\\\\phi"<<"\\\\chi"
              <<"\\\\psi"<<"\\\\omega"<<"\\\\varGamma"<<"\\\\varSigma"<<"\\\\varTheta"
              <<"\\\\varLambda"<<"\\\\varkappa"<<"\\\\varpi"<<"\\\\varsigma"
             <<"\\\\varepsilon"<<"\\\\varOmega"<<"\\\\emptyset"<<"\\\\square"
                    ;
     foreach (const QString &pattern, greekList) {
         rule.pattern = QRegExp(pattern);
         rule.format = equationGreeks;
         highlightingRules.append(rule);
     }


     //keywords in equations
     equationKeyword.setForeground(Qt::darkGreen);
     QStringList eqkeyList;
     eqkeyList<<"\\\\sum"<<"\\\\in\\b"<<"\\\\notin"<<"\\\\leq\\b"<<"\\\\geq\\b"<<"\\\\equiv\\b"<<"\\\\sin"<<"\\\\cos"
             <<"\\\\frac"<<"\\\\bm"<<"\\\\ldots\\b"<<"\\\\sim"<<"\\\\text"<<"\\\\prod"<<"\\\\cdot"
             <<"\\\\infty"<<"\\\\limits"<<"\\\\nonumber"<<"\\\\forall"<<"\\\\cup"<<"\\\\wedge"
             <<"\\\\left"<<"\\\\right"<<"\\\\lfloor"<<"\\\\rfloor"<<"\\\\lceil"<<"\\\\rceil"
             <<"\\\\mathcal"<<"\\\\max"<<"\\\\min"<<"\\\\int"<<"\\\\times"<<"\\\\cap\\b"
            <<"\\\\mathop"<<"\\\\sup"<<"\\\\leftarrow"<<"\\\\rightarrow"<<"\\\\exp\\b"
           <<"\\\\Rightarrow"<<"\\\\Leftarrow"<<"\\\\bar"<<"\\\\hat"<<"\\\\tilde"
               ;
     foreach (const QString &pattern, eqkeyList) {
         rule.pattern = QRegExp(pattern);
         rule.format = equationKeyword;
         highlightingRules.append(rule);
     }

     //////


     classFormat.setFontWeight(QFont::Bold);
     classFormat.setForeground(Qt::darkMagenta);
     rule.pattern = QRegExp("\\bQ[A-Za-z]+\\b");
     rule.format = classFormat;
     highlightingRules.append(rule);



     //actually equations in LaTeX
     multiLineCommentFormat.setForeground(Qt::darkYellow);//magenta);
     multiLineCommentFormat.setFontItalic(false);

     quotationFormat.setForeground(Qt::darkGreen);
     rule.pattern = QRegExp("\".*\"");
     rule.format = quotationFormat;
     highlightingRules.append(rule);

     //quotationFormat.setForeground(Qt::darkGreen);
     //rule.pattern = QRegExp("\\$.*\\$");
     //rule.format = quotationFormat;
     //highlightingRules.append(rule);

     functionFormat.setFontItalic(true);
     functionFormat.setForeground(Qt::blue);
     rule.pattern = QRegExp("\\b[A-Za-z0-9_]+(?=\\()");
     rule.format = functionFormat;
     //highlightingRules.append(rule);

     commentStartExpression = QRegExp("\\$");//("/\\*");
     commentEndExpression = QRegExp("\\$");

     singleLineCommentFormat.setForeground(Qt::green);
     singleLineCommentFormat.setFontItalic(true);
     /*  not enough
     //rule.pattern = QRegExp("//[^\n]*");
     rule.pattern = QRegExp("\%[^\n]*");
     rule.format = singleLineCommentFormat;
     highlightingRules.append(rule);
     */

     spellErrorFormat.setUnderlineColor(Qt::red);
     spellErrorFormat.setUnderlineStyle(QTextCharFormat::DotLine);

     sChecker=NULL;
     QString basePath = QDir::homePath()+"/.TeXpen/dicts/";
     QDir a(basePath);
     if(!a.exists(basePath)){
         a.mkpath(basePath);
     }
     QString dicfile=QDir::homePath()+"/.TeXpen/dicts/en_GB.dic";
     QString afffile=QDir::homePath()+"/.TeXpen/dicts/en_GB.aff";
     QString dicfile2=QDir::homePath()+"/.TeXpen/dicts/en_US.dic";
     QString afffile2=QDir::homePath()+"/.TeXpen/dicts/en_US.aff";
     //QFile b(dicfile);
     if(!QFile::exists(dicfile)){
         QFile::copy(":/dicts/en_GB.dic", dicfile);
         QFile::copy(":/dicts/en_GB.aff", afffile);       
    }
    if(!QFile::exists(dicfile2)){
         QFile::copy(":/dicts/en_US.dic", dicfile2);
         QFile::copy(":/dicts/en_US.aff", afffile2);
     }
     setDict(dicfile2);
     spellCheckActive=true;


     findFormat.setBackground(QColor(Qt::yellow).lighter(160));
     findFormat.setForeground(QColor(Qt::black));
     FindStr = "";

 }


 void Highlighter::enableSpellCheck(bool b){
     bSpellCheck=b;
     rehighlight();
 }

 void Highlighter::setFind(QString sel, bool isCase){
     FindStr = sel;
     if(!FindStr.isEmpty()){
         isCaseSensetive= isCase;
         rehighlight();
     }
     //QtConcurrent::run(this, &Highlighter::rehighlight);
     rehighlight();
 }


 bool inTexComment(const QString &text, const int pos){
     int idx = text.indexOf("\n");
     if(idx>pos || idx<0){
         //pos is in first line;
         idx = 0;
     }else{
         int j=text.indexOf("\n",idx);
         while(j<pos && j>=0){
             idx = j;
             j=text.indexOf("\n",idx);
         }
         //now idx indicates the nearest newline
         idx +=1;
     }
     QString cnt = text.mid(idx,pos-idx);
     if(cnt.isEmpty()){
         return false;
     }
     if(!cnt.contains("\%")){
         return false;
     }
     return isTexComment(cnt);
 }


 void Highlighter::highlightComment(const QString &text){
    int idx = text.indexOf("\%");
    if(idx<0){
        return;
    }
    int pos = -1;
    if(idx==0){
        //the whole block
        pos = 0;
    }
    else if(text.at(idx-1) != '\\'){
        //from idx to '\n'
        pos = idx;
    }else{
        while(idx>0 && text.at(idx-1) == '\\'){
            idx = text.indexOf("%", idx+1);
        }
        pos = idx;
    }
    if(idx>=0){
        int end = text.length();//indexOf("\n",pos);
        setFormat(pos, end-pos, singleLineCommentFormat);
    }
 }


 bool inEquation=false;

 void Highlighter::highlightTexEquations(const QString &text, QString beginToken, QString endToken){
     int startIdx = text.indexOf(beginToken);
     int endIdx   = text.indexOf(endToken);
     int length = -1;
     if(startIdx <0 && (!inEquation) ){
         return;
     }else{
     if(startIdx>=0 && (! inTexComment(text,startIdx))){
         inEquation = true;
         //qDebug()<<"Entering equation..."<<endl;
     }
     if(endIdx>=0){
         if (! inTexComment(text,endIdx)){
            length = endIdx - startIdx + 15; //\end{equation} length 15
            inEquation = false;
            //qDebug()<<"end of an equation."<<endl;
         }else{
             //qDebug()<<"(a commented \\end{equation}) still in equation..."<<endl;
             length = text.length();
         }
     }else{
         //qDebug()<<"still in equation..."<<endl;
         length = text.length();
     }

     if(startIdx<0){startIdx = 0;}
     if(length<0){length = 0;}
     setFormat(startIdx, length, multiLineCommentFormat);
    }
 }




 void Highlighter::highlightTexEquations(const QString &text){

     ///  \begin{equation}....\end{equation}
     ///   $$ ... $$
     ///  \begin{falign} ... \end{falign}
     QRegExp begin = QRegExp("\\\\begin\\{equation\\}");//QRegExp("^\\\\begin\\s*\{\\s*equation\\s*}$");
     QRegExp end = QRegExp("\\\\end\\{equation\\}");//("^\\\\end\\s*\\{\\s*equation\\s*\\}$");

     int startIdx = text.indexOf("\\begin{equation}");
     int endIdx   = text.indexOf("\\end{equation}");
     int length = -1;
     if(startIdx <0 && (!inEquation) ){
         //return;
     }else{
     if(startIdx>=0 && (! inTexComment(text,startIdx))){
         inEquation = true;
         //qDebug()<<"Entering equation..."<<endl;
     }
     if(endIdx>=0){
         if (! inTexComment(text,endIdx)){
            length = endIdx - startIdx + 15; //\end{equation} length 15
            inEquation = false;
            //qDebug()<<"end of an equation."<<endl;
         }else{
             //qDebug()<<"(a commented \\end{equation}) still in equation..."<<endl;
             length = text.length();
         }
     }else{
         //qDebug()<<"still in equation..."<<endl;
         length = text.length();
     }

     if(startIdx<0){startIdx = 0;}
     if(length<0){length = 0;}

     setFormat(startIdx, length, multiLineCommentFormat);
    }

     //$$
     startIdx = text.indexOf("$$");
     endIdx   = text.indexOf("$$");
     length = -1;
     if(startIdx <0 && (!inEquation) ){
         //return;
     }else{
     if(startIdx>=0 && (! inTexComment(text,startIdx))){
         inEquation = true;
         //qDebug()<<"Entering equation..."<<endl;
     }
     if(endIdx>=0){
         if (! inTexComment(text,endIdx)){
            length = endIdx - startIdx + 2; //$$ length 2
            inEquation = false;
            //qDebug()<<"end of an equation."<<endl;
         }else{
             //qDebug()<<"(a commented \\end{equation}) still in equation..."<<endl;
             length = text.length();
         }
     }else{
         //qDebug()<<"still in equation..."<<endl;
         length = text.length();
     }

     if(startIdx<0){startIdx = 0;}
     if(length<0){length = 0;}

     setFormat(startIdx, length, multiLineCommentFormat);
    }


     //\[ \]
     startIdx = text.indexOf("\\[");
     endIdx   = text.indexOf("\\]");
     length = -1;
     if(startIdx <0 && (!inEquation) ){
         //return;
     }else{
     if(startIdx>=0 && (! inTexComment(text,startIdx))){
         inEquation = true;
         //qDebug()<<"Entering equation..."<<endl;
     }
     if(endIdx>=0){
         if (! inTexComment(text,endIdx)){
            length = endIdx - startIdx + 2; //\] length 2
            inEquation = false;
            //qDebug()<<"end of an equation."<<endl;
         }else{
             //qDebug()<<"(a commented \\end{equation}) still in equation..."<<endl;
             length = text.length();
         }
     }else{
         //qDebug()<<"still in equation..."<<endl;
         length = text.length();
     }

     if(startIdx<0){startIdx = 0;}
     if(length<0){length = 0;}

     setFormat(startIdx, length, multiLineCommentFormat);
    }

     return;



     setCurrentBlockState(0);

     int startIndex = 0;
     if (previousBlockState() != 1){
         startIndex = begin.indexIn(text);
         qDebug()<<"startIndex="<<startIndex<<endl;
         if(! inTexComment(text,startIndex)){
             startIndex = -1;
         }
     }

     //while (startIndex >= 0 || previousBlockState() == 1) {
         int endIndex = end.indexIn(text, startIndex+1);
         qDebug()<<"endIndex="<<endIndex<<endl;
         if(! inTexComment(text,endIndex)){
             endIndex = -1;
         }

         int commentLength;
         if (endIndex == -1) {
             setCurrentBlockState(1);
             commentLength = text.length() - startIndex;
         } else {
             commentLength = endIndex - startIndex
                             + end.matchedLength();
             setCurrentBlockState(0);
         }

         if(! inTexComment(text,startIndex)){
            setFormat(startIndex, commentLength, multiLineCommentFormat);
         }
      //   startIndex = begin.indexIn(text, startIndex + commentLength +1);//+1 wangmenchang
     //}

 }


 void Highlighter::spellCheck(const QString &text)
 {
     if (spellCheckActive) {
         // split text into words
         QString str = text.simplified();
         if (!str.isEmpty()) {
             QStringList Checkliste = str.split(QRegExp("([^\\w,^\\\\]|(?=\\\\))+"),
                     QString::SkipEmptyParts);
             int l,number;
             // check all words
             for (int i=0; i<Checkliste.size(); ++i) {
                 str = Checkliste.at(i);
                 if (str.length()>1 &&!str.startsWith('\\'))
                 {
                     if (!checkWord(str)) {
                         number = text.count(QRegExp("\\b" + str + "\\b"));
                         l=-1;
                         // underline all incorrect occurences of misspelled word
                         for (int j=0;j < number; ++j)
                         {
                             l = text.indexOf(QRegExp("\\b" + str + "\\b"),l+1);
                             if (l>=0)
                                 if(!inTexComment(text,l)){
                                    setFormat(l, str.length(), spellErrorFormat);
                                 }
                         } // for j
                     } // if spell chek error
                 } // if str.length > 1
             } // for
         } // if str.isEmpty
     } // initial check
 }

 bool Highlighter::checkWord(QString word)
 {
     int check;
     QByteArray encodedString;
     encodedString = codec->fromUnicode(word);
     check = sChecker->spell(encodedString.data());
     return bool(check);
 }

 bool Highlighter::setDict(const QString SpellDic)
 {
     bool spell;
     if(SpellDic!=""){
         //mWords.clear();
         spell_dic=SpellDic.left(SpellDic.length()-4);
         if(sChecker!=NULL){
            delete sChecker;
             sChecker=NULL;
         }
         sChecker = new Hunspell(spell_dic.toLatin1()+".aff",spell_dic.toLatin1()+".dic");
         spell_encoding=QString(sChecker->get_dic_encoding());
         codec = QTextCodec::codecForName(spell_encoding.toLatin1());

         QFileInfo fi(SpellDic);

         if (fi.exists() && fi.isReadable()) spell=true;
         else spell=false;

         // get user config dictionary
         //temperarily commented 07-21
         /*
         QSettings setting;
         QString filePath=QFileInfo(setting.fileName()).absoluteFilePath();
         filePath=filePath+"/User_"+QFileInfo(spell_dic.toLatin1()+".dic").fileName();
         qDebug()<< qPrintable(filePath) << endl;
         fi=QFileInfo(filePath);
         if (fi.exists() && fi.isReadable()){
             sChecker->add_dic(filePath.toLatin1());
         }
         else filePath="";
         */

         //spellErrorFormat.setForeground(Qt::red);//faster Cursoroperation ...
         //spellCheckFormat.setUnderlineColor(QColor(Qt::red));
         spellErrorFormat.setUnderlineStyle(QTextCharFormat::DotLine);//QTextCharFormat::SpellCheckUnderline);
     }
     else spell=false;
     spellerError=!spell;
     spellCheckActive=spellCheckActive && spell;
     rehighlight();
     return spell;
 }




 void Highlighter::highlightBlock(const QString &text)
 {

     highlightTexEquations(text);

     setCurrentBlockState(0);

     int startIndex = 0;
     if (previousBlockState() != 1)
         startIndex = commentStartExpression.indexIn(text);

     while (startIndex >= 0) {
         int endIndex = commentEndExpression.indexIn(text, startIndex+1);
         int commentLength;
         if (endIndex == -1) {
             setCurrentBlockState(1);
             commentLength = text.length() - startIndex;
         } else {
             commentLength = endIndex - startIndex
                             + commentEndExpression.matchedLength();
         }

         if(! inTexComment(text,startIndex)){
            setFormat(startIndex, commentLength, multiLineCommentFormat);
         }
         startIndex = commentStartExpression.indexIn(text, startIndex + commentLength +1);//+1 wangmenchang
     }


     foreach (const HighlightingRule &rule, highlightingRules) {
         QRegExp expression(rule.pattern);
         int index = expression.indexIn(text);
         while (index >= 0) {
             int length = expression.matchedLength();
             setFormat(index, length, rule.format);
             index = expression.indexIn(text, index + length);
         }
     }

     highlightComment(text);

     if(bSpellCheck){
        spellCheck(text);
     }

     if(!FindStr.isEmpty()){
         int fdpos =-1;
         if(isCaseSensetive){
            fdpos=text.indexOf(FindStr,0,Qt::CaseSensitive);}
         else{
             fdpos=text.indexOf(FindStr,0,Qt::CaseInsensitive);
         }
         while(fdpos>=0){
             setFormat(fdpos,FindStr.length(),findFormat);
             if(isCaseSensetive){
                fdpos = text.indexOf(FindStr,fdpos+FindStr.length(),Qt::CaseSensitive);
             }else{
                 fdpos = text.indexOf(FindStr,fdpos+FindStr.length(),Qt::CaseInsensitive);
             }
         }
     }
 }





// TODO: Check how to to this.
